﻿using System;
using System.Data;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Xml;
using Microsoft.SharePoint;

namespace SharePointSearchBench.Library
{
    public class CamlQuery : BaseQuery
    {
        string viewFields;
        CamlQueryScope scope;
        CamlCondition condition;
        int rowLimit;
        SPList list;
        SPWeb web;
        int listServerTemplate;
        int listBaseType;
        string viewName;

        private CamlQuery(string viewFieldsXml)
        {
            this.rowLimit = 1000;
            this.viewFields = viewFieldsXml;
        }

        private CamlQuery(StringCollection fields)
        {
            this.rowLimit = 1000;

            string format = "<FieldRef Name=\"{0}\"/>";
            StringBuilder text = new StringBuilder();
            foreach(string item in fields)
            {
                text.AppendFormat(format, item);
            }
            this.viewFields = text.ToString();
        }

        public CamlQueryScope Scope
        {
            get { return this.scope; }
            set { this.scope = value; }
        }

        public int RowLimit
        {
            get { return this.rowLimit; }
            set { this.rowLimit = value; }
        }

        public CamlQuery From(SPList list)
        {
            this.web = null;
            this.list = list;
            return this;
        }

        public CamlQuery From(SPWeb web)
        {
            this.web = web;
            this.list = null;
            return this;
        }

        public CamlQuery WithViewName(string view)
        {
            this.viewName = view;
            return this;
        }

        public CamlQuery WithListServerTemplate(int serverTemplateId)
        {
            this.listServerTemplate = serverTemplateId;
            return this;
        }

        public CamlQuery WithListBaseType(int baseTypeId)
        {
            this.listBaseType = baseTypeId;
            return this;
        }

        public static CamlQuery Select(string viewFieldsXml)
        {
            return new CamlQuery(viewFieldsXml);
        }

        public static CamlQuery Select(StringCollection fields)
        {
            return new CamlQuery(fields);
        }

        public CamlQuery Where(CamlCondition condition)
        {
            this.condition = condition;
            return this;
        }

        public DataTable Execute()
        {
            DataTable results = null;
            if(this.list != null)
            {
                if(this.ApiSource == ApiSource.Service)
                {
                    throw new NotSupportedException("Service calls on list-scoped CAML queries are not yet supported.");
                }

                SPQuery query = new SPQuery();
                query.ViewFields = this.viewFields;
                query.Query = this.BuildQueryText();
                query.RowLimit = Convert.ToUInt32(this.rowLimit);
                SPListItemCollection items = null;
                if(!string.IsNullOrEmpty(this.viewName))
                {
                    items = list.GetItems(query, this.viewName);
                }
                else
                {
                    items = list.GetItems(query);
                }
                results = this.ConvertListItemsToDataTable(items);
            }
            else if (this.web != null)
            {
                if(this.ApiSource == ApiSource.Service)
                {
                    throw new NotSupportedException("Service calls on web-scoped CAML Queries (SPSiteDataQuery) are not supported.");
                }

                SPSiteDataQuery query = new SPSiteDataQuery();
                query.Query = this.BuildQueryText();
                query.ViewFields = this.viewFields;
                query.RowLimit = Convert.ToUInt32(this.rowLimit);
                query.Lists = this.BuildListsElement();
                query.Webs = this.BuildWebsElement();
                results = web.GetSiteData(query);
            }

            return results;
        }

        DataTable ConvertListItemsToDataTable(SPListItemCollection listItems)
        {
            XmlDocument doc = new XmlDocument();
            doc.LoadXml("<root>" + this.viewFields + "</root>");
            string query = "//FieldRef";
            XmlNodeList nodes = doc.SelectNodes(query);

            DataTable table = new DataTable();
            SPListItem firstItem = null;
            if (listItems.Count > 0)
            {
                firstItem = listItems[0];
            }

            foreach(XmlElement element in nodes)
            {
                DataColumn column = new DataColumn(element.Attributes["Name"].Value);
                if (firstItem != null)
                {
                    column.DataType = firstItem[column.ColumnName].GetType();
                }

                table.Columns.Add(column);
            }

            foreach(SPListItem item in listItems)
            {
                DataRow row = table.NewRow();

                foreach(DataColumn column in table.Columns)
                {
                    row[column.ColumnName] = item[column.ColumnName];
                }
                table.Rows.Add(row);
            }
            return table;
        }

        private string BuildQueryText()
        {
            return "<Where>" + this.condition.ToString() + "</Where>";
        }

        private string BuildWebsElement()
        {
            string baseWebXml = "<Webs Scope=\"{0}\"/>";
            if(this.scope == CamlQueryScope.Recursive)
            {
                return string.Format(baseWebXml, "Recursive");
            }
            else
            {
                return string.Format(baseWebXml, "SiteCollection");
            }
        }

        private string BuildListsElement()
        {
            string element = "<Lists {0}{1}/>";
            string serverTemplatePart = string.Empty;
            string baseListPart = string.Empty;

            if(this.listServerTemplate > 0)
            {
                serverTemplatePart = "ServerTemplate=\"" + this.listServerTemplate.ToString() + "\" ";
            }

            if(this.listBaseType > 0)
            {
                serverTemplatePart = "BaseType=\"" + this.listBaseType.ToString() + "\" ";
            }

            return string.Format(element, serverTemplatePart, baseListPart);
        }
    }
}
